class Celda():
    
    def __init__(self, pressure=0, new_pres=0):
        '''Inicializa todos los valores con pressure y los new con new_pressure.'''
        self.visitado = False
        self.visitas = []
        self.pressure = pressure
        self.new_pressure = new_pres
        
    
    def get_pressure(self):
        '''Devuelve el valor de pressure'''
        return self.pressure
    
    def get_new_pressure(self):
        '''Devuelve el valor de new_pressure'''
        return self.new_pressure
    
    def set_pressure(self, value):
        '''Toma un valor y setea pressure con ese valor.'''
        self.pressure = value
    
    def set_new_pressure(self, value):
        '''Toma un valor y setea new_pressure con ese valor.'''
        self.new_pressure = value
    
    def update_new_pressure(self, value):
        '''Suma 'value' al new_pressure.'''
        self.new_pressure += value
    
    def update(self):
        '''Actualiza pressure con el valor de new_pressure.'''
        self.pressure = self.new_pressure
        
    def __repr__(self):
        ret = '%s'%(self.get_pressure())
        return ret

def clamp(value, max, min):
        '''
        Funcion clamp, devuelve value si value esta entre min y max, devuelve min o max si no.
        '''
        if value<min:
            return min
        if value>max:
            return max
        return value


class Matrix():
    """Creo una matriz que sera una lista de listas
    la lista inicial tendra en cada posicion las filas
    de la matriz:
    Ejemplo de matriz 4xn
        [fila1, fila2, fila3, fila4]
    Donde fila_i es una lista de longitud n"""
    
    def __init__(self, rows, cols):
        '''Inicializa una matriz de NxM donde N=rows y M=cols. 
        Todas las celdas se inicializan en 0'''
        self.cols = cols
        self.rows = rows
        
        # Inicializo matriz
        self.matrix = []
        
        for i in range(rows):
            # Creo una fila nuevo
            row = []
            
            for j in range(cols):
                # Lleno la fila con 0 en cada celda
                celda = Celda()
                row.append(celda)
            
            self.matrix.append(row)
        
    def get_value(self, row, col):
        ''' Devuelve el valor de la celda en (row,col)'''
        
        # Si se pide una celda fuera de la matriz
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            
            # Por defecto devuelvo el valor de una celda muerta
            return 0
        return self.get_cell(row,col).get_pressure()
    
    def get_pressure(self, row, col):
        ''' Devuelve el valor de la celda en (row,col)'''
        
        return self.get_cell(row,col).get_pressure()
    
    def get_cell(self, row, col):
        '''Devuelve la celda ubicada en (row,col). De no estar definida devuelve
        una celda nueva, con todos valores 0.'''
        
        if self.rows < row or row < 1 or self.cols < col or col < 1:
            # Por defecto devuelvo el valor de una celda muerta
            return Celda()
        return self.matrix[row-1][col-1]

    def update_cell(self, row, col, value):
        '''Actualiza el valor de pressure y new_pressure de (row,col) con value'''
        c = self.get_cell(row,col)
        c.set_new_pressure( value)
        c.update()
        
    def set_value(self, row, col, value):
        ''' Le asigna un valor a la presion'''
        # Si se pide una celda fuera de la matriz
        self.get_cell(row,col).set_pressure( value )
        
    def vecinos(self, row, col):
        ''' Devuelve una lista con los vecinos de la vecindad de Moore. Son los 
        vecinos de los puntos cardinales y las diagonales. Es una lista de celdas'''
        
        ret = {'n':2 ,'s': 2,'e': 2, 'o': 2, 'ne':2 ,'se':2 , 'no':2 ,'so':2}
        
        # Vecino del Norte
        ret['n'] = self.get_cell(row-1,col)
        # Vecino del Sur
        ret['s'] = self.get_cell(row+1,col)
        # Vecino del Este
        ret['e'] = self.get_cell(row,col+1)
        # Vecino del Oeste
        ret['o'] = self.get_cell(row,col-1)
        
        # Vecino del Norte-Este
        ret['ne'] = self.get_cell(row-1,col+1)
        # Vecino del Sur-Este
        ret['se'] = self.get_cell(row+1,col+1)
        # Vecino del Norte-Oeste
        ret['no'] = self.get_cell(row-1,col-1)
        # Vecino del Sur-Oeste
        ret['so'] = self.get_cell(row+1,col-1)
        
        return ret
    
    def vecinos_neumann(self, row, col):
        ''' Devuelve una lista con los vecinos de la vecindad de Neumann. Son
        los vecinos de los 4 puntos cardinales. Es una lista de celdas.
        '''
        ret = {'n':2 ,'s': 2,'e': 2, 'o': 2}
        
        # Vecino del Norte
        ret['n'] = self.get_cell(row-1,col)
        # Vecino del Sur
        ret['s'] = self.get_cell(row+1,col)
        # Vecino del Este
        ret['e'] = self.get_cell(row,col+1)
        # Vecino del Oeste
        ret['o'] = self.get_cell(row,col-1)
        
        return ret
    
    def rules_pressure(self):

        # Devuelvo una lista con las celdas que cambiaron
        changes = []
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
            
                # Obtengo los vecinos de la celda
                vecinos = self.vecinos(i,j)
                #old_pressure = self.get_cell(i,j).new_pressure 
                
                # Actualizo puntos cardinales 
                puntos = ['n', 's', 'e', 'o', 'ne','se', 'no','so']
                for punto in puntos:
                    celda = self.get_cell(i,j)
                    # Obtengo valor de la celda que estoy viendo
                    valor = celda.get_value(punto)
                    if valor == 0:
                        continue
                    
                    flow = 0.14
                    value = flow * valor
                    print 'celda %s'
                    '''Para el vecino de cada punto cardinal, actualizo su presion
                    pero en el punto cardinal que corresponde'''
                    vecinos[punto].update_puntos_new(value, punto)
                    
                    # Ahora se le resta a la celda que estoy viendo la cantidad 'value'
                    # que es lo que se transfirio a la celda vecina
                    celda.update_puntos_new(-value, punto)
                    
        return changes

    def update_matrix(self):
        
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
                self.get_cell(i,j).update()
                
        return 

    def rules(self):
        '''
        Recorro la matriz. Para cada vecino de la celda:
            1) Obtengo la diferencia de presion 
            2) Uso la funcion clamp() para ajustar el valor a pasar
            3) Transfiero el valor a la celda vecina (a new_pressure)
            4) Resto lo que transferi a la celda que estoy visitando
        '''
        flow = 0.15
        
        for i in range(1, self.rows+1):
            for j in range(1, self.cols+1):
                
                # Obtengo la celda y la marco visitada
                celda = self.get_cell(i,j)
                
                # Obtengo los vecinos de neumann (4 vecinos)
                vecinos = self.vecinos_neumann(i,j)
                
                # Actualizo puntos cardinales 
                for punto, vecino in vecinos.items():
                    
                    # Obtengo la diferencia de presion
                    dpres = celda.get_pressure() - vecino.get_pressure()
                    
                    # Ajusto el valor a transferir
                    valor = clamp (flow*dpres, celda.get_pressure()/4, -vecino.get_pressure()/4)
                    
                    # Transfiero el valor al vecino
                    vecino.update_new_pressure( valor)
                    
                    # Resto lo que transferi
                    celda.update_new_pressure(-valor)
                    
        return 
    
    def get_force(self, row, col):
        '''Devuleve (F_x, F_y), la fuerza que siente la celda (row, col)'''
        
        # Obtengo presion de la celda (row, col)
        pres = self.get_pressure(row, col)
        
        vecinos = self.vecinos_neumann(row, col)
        
        # Obtengo diferencia de presion entre (row+1, col) y (row-1, col)
        x_dpres = vecinos['e'].get_pressure() - vecinos['o'].get_pressure()
        
        # Obtengo diferencia de presion entre (row, col+1) y (row, col-1)
        y_dpres = vecinos['s'].get_pressure() - vecinos['n'].get_pressure()
        
        # y_dpres > 0 => fuerza hacia abajo
        # x_dpres > 0 => fuerza hacia la derecha
        fx = x_dpres * .3
        fy = y_dpres * .3
        
        return [fx, fy]
        
    def __repr__(self):
        
        ret = ''
        for i in range(self.rows):
            ret += 'Fila %s = %s \n'%(i+1,self.matrix[i])
        
        return ret
        